import libtcodpy as libtcod

import console

SCROLL_DELAY = 0.20  # 250 ms is too slow

class Menu(object):
    def __init__(self, width, height, title, items, header='', footer='', callback=None):
        self.width = width
        self.height = height
        self.title = title

        self._fore = libtcod.white
        self._back = libtcod.black
        self._con = console.Console(self.width, self.height)

        self.header = header
        self.footer = footer
        self.items = items
        self.selection = 0
        self.err = False
        self.wrap = False

        self.scroll_wait = SCROLL_DELAY
        self.scroll_ix = 0
        self.scroll_dir = 1

        self.callback = callback

#        self.update()

    def update(self):
        self._con.frame(0, 0, self.width, self.height, True, libtcod.BKGND_SET, self.title)

        line = 2
        if self.header:
            self._con.print_center(self.width / 2, 1, libtcod.BKGND_NONE, self.header)
            line += 1

        show = self.height - bool(self.header) - line
        more = False
        if len(self.items) > show:
            if (self.selection + 1) >= show:
                offset = self.selection - show + 3
    
                # Top More
                self._con.print_center(self.width / 2, line, libtcod.BKGND_NONE, '--- MORE ---')
                line += 1
                show -= 1
    
                # Bottom More
                if self.selection < (len(self.items) - 1):
                    show -= 1
                    more = True
            else:
                offset = 0
                if show < len(self.items):
                    show -= 1  # Leave room for --- more --- on bottom
                    more = True
            show = min(show, len(self.items))
        else:
            offset = 0

        num_width = len(str(len(self.items)))
        for ix, item in enumerate(self.items[offset:offset+show]):
            fmt = "%s. %s" % (str(ix+offset+1).rjust(num_width), smart_cap(str(item)))


            if (ix + offset) == self.selection:
                # Highlight the selected item
                self._con.background = self._fore
                self._con.foreground = self._back

                # If the item is too long, scroll the text.
                if len(fmt) > (self.width - 2):
                    max_offset = len(fmt) - (self.width - 3)
                    self.scroll_wait -= libtcod.sys_get_last_frame_length()

                    if self.scroll_wait < 0:
                        self.scroll_ix += self.scroll_dir
                        self.scroll_wait = SCROLL_DELAY
                    
                    if self.scroll_ix < 0:
                        self.scroll_ix = 0 # 1  # Don't bounce back right away
                        self.scroll_dir = 1
                    elif self.scroll_ix > max_offset:
                        self.scroll_ix = max_offset # - 1
                        self.scroll_dir = -1

                    scroll = smart_cap(str(item)[self.scroll_ix:])
                    fmt = ("%s. %s" % (str(ix+offset+1).rjust(num_width), scroll))[:self.width-2]
                self._con.print_left(1, line, libtcod.BKGND_SET, fmt.ljust(self.width - 2))
            else:
                self._con.background = self._back
                self._con.foreground = self._fore

                if len(fmt) > (self.width - 2):
                    fmt = fmt[:self.width-3]
                    self._con.print_left(1, line, libtcod.BKGND_SET, fmt.ljust(self.width - 2))
                    self._con.putchar(self.width - 2, line, libtcod.CHAR_ARROW_E, libtcod.BKGND_SET)
                else:
                    self._con.print_left(1, line, libtcod.BKGND_SET, fmt.ljust(self.width - 2))

            line += 1

        # Undo highlight if last item was selected
        self._con.background = self._back
        self._con.foreground = self._fore
        if more:
            self._con.print_center(self.width / 2, line, libtcod.BKGND_NONE, '--- MORE ---')

        # The footer is drawn over the bottom border
        if self.footer:
            self._con.print_center(self.width / 2, self.height - 1, libtcod.BKGND_NONE, self.footer)


    def show(self, parent, x, y, foreground_alpha=1.0, background_alpha=1.0):
        # Save the background to replace when done with the menu
        bak = console.Console(self.width, self.height)
        if hasattr(parent, 'blit'):
            parent.blit(x, y, self.width, self.height, bak, 0, 0) 
        elif parent == 0:
            libtcod.console_blit(0, x, y, width, height, bak, 0, 0)
        else:
            raise console.ConsoleError("Cannot blit from %r" % repr(parent))

        self.err = False
        while True:
            # Draw the menu
            self.update()
            if self.callback:
                self.callback(self.items[self.selection])

            bak.blit(0, 0, self.width, self.height, parent, x, y)
            self._con.blit(0, 0, self.width, self.height, parent, x, y, foreground_alpha, background_alpha)
            libtcod.console_flush()

            # Check for keypress
            key = libtcod.console_check_for_keypress(True)
            if key.vk == libtcod.KEY_NONE:
                continue
            elif key.vk == libtcod.KEY_ESCAPE:
                self.err = True
                break
            elif key.vk in (libtcod.KEY_KPENTER, libtcod.KEY_ENTER):
                break
            elif key.vk in (libtcod.KEY_KP8, libtcod.KEY_UP):
                self.selection -= 1
                if self.selection < 0:
                    self.selection = (len(self.items) - 1) if self.wrap else 0
                self.scroll_wait = SCROLL_DELAY
                self.scroll_ix = 0
                self.scroll_dir = 1
            elif key.vk in (libtcod.KEY_KP2, libtcod.KEY_DOWN):
                self.selection += 1
                if self.selection > (len(self.items) - 1):
                    self.selection = 0 if self.wrap else (len(self.items) - 1)
                self.scroll_wait = SCROLL_DELAY
                self.scroll_ix = 0
                self.scroll_dir = 1

        # Restore the background from our bak console
        bak.blit(0, 0, self.width, self.height, parent, x, y)
        libtcod.console_flush()

        if not self.err:
            return self.items[self.selection]

    @property
    def foreground(self):
        return self._fore
    @foreground.setter
    def fourground(self, colour):
        self._fore = colour

    @property
    def background(self):
        return self._back
    @background.setter
    def fourground(self, colour):
        self._back = colour

def smart_cap(s):
    return ''.join((s[0].upper(), s[1:]))


if __name__ == '__main__':
    import random
    from pprint import pprint

    libtcod.sys_set_fps(30)
    libtcod.console_set_custom_font('fonts/arial10x10.png', libtcod.FONT_TYPE_GREYSCALE | libtcod.FONT_LAYOUT_TCOD)
    root = console.RootConsole(80, 40, 'Title')

    colours = [libtcod.red, libtcod.orange, libtcod.yellow, libtcod.chartreuse, libtcod.green, 
               libtcod.sea, libtcod.cyan, libtcod.sky, libtcod.blue, libtcod.violet, libtcod.magenta, 
               libtcod.pink, libtcod.white, libtcod.black, libtcod.grey,
              ] 
    for y in range(40):
        for x in range(80):
            root.set_back(x, y, random.choice(colours), libtcod.BKGND_SET)
    root.flush()


    items = ['Item %s' % (i + 0) for i in range(10)]
    items.append('A very long item that will scroll.')
    items.extend(['Item X%s' % (i + 0) for i in range(2)])
    pprint(items)

    menu = Menu(20, 20, 'Test Menu', items, header='[Header]', footer='[Footer]')
    menu.wrap = True
    sel = menu.show(root, 20, 5, foreground_alpha=1.0, background_alpha=0.8)

    print sel
    print "The original background should be here.  Press any key..."
    root.wait_for_keypress(True)



